package cc.aliza.lib.mongo.core.decoder;

import cc.aliza.lib.mongo.core.annotations.Default;
import cc.aliza.lib.mongo.core.annotations.Property;
import cc.aliza.lib.mongo.core.utils.DataType;
import cc.aliza.lib.mongo.core.utils.FieldUtil;
import org.bson.Document;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.Timestamp;
import java.util.*;

/**
 * 『给时光以生命·给岁月以文明』
 *
 * @author Zhang Jing (byrdkm17@gmail.com).
 */
@SuppressWarnings("unchecked")
public class PropertyDecoder extends AbstractDecoder {

    public PropertyDecoder(Document document, Field field) {
        super(field);
        Property property = field.getAnnotation(Property.class);
        String name = Default.NAME;
        if (property != null) {
            name = property.name();
        }
        value = document.get(getFieldRealName(name));
    }

    @Override
    public void decode(Object obj) {
        Class<?> type = field.getType();
        if (type.isArray()) {
            Class comType = type.getComponentType();
            if (DataType.isByte(comType)) {
                decodeBinary(obj);
            } else {
                decodeArray(obj, comType);
            }
        } else {
            decodePrimitive(obj, type);
        }
    }

    private void decodeBinary(Object obj) {
        FieldUtil.set(obj, field, value);
    }

    private void decodeArray(Object obj, Class<?> comType) {
        List list = (ArrayList) value;
        if (DataType.isString(comType)) {
            List<String> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(o.toString()));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isInteger(comType)) {
            List<Integer> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Integer.parseInt(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isIntegerObject(comType)) {
            List<Integer> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Integer.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isLong(comType)) {
            List<Long> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Long.parseLong(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isLongObject(comType)) {
            List<Long> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Long.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isShort(comType)) {
            List<Short> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Short.parseShort(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isShortObject(comType)) {
            List<Short> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Short.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isByteObject(comType)) {
            List<Byte> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Byte.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isFloat(comType)) {
            List<Float> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Float.parseFloat(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isFloatObject(comType)) {
            List<Float> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Float.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isDouble(comType)) {
            List<Double> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Double.parseDouble(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isDoubleObject(comType)) {
            List<Double> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Double.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isBoolean(comType)) {
            List<Boolean> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Boolean.parseBoolean(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isBooleanObject(comType)) {
            List<Boolean> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(Boolean.valueOf(o.toString())));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isChar(comType) || DataType.isCharObject(comType)) {
            List<Character> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add(o.toString().charAt(0)));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isDate(comType)) {
            List<Date> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add((Date) o));
            FieldUtil.set(obj, field, arr.toArray());
        }
        if (DataType.isTimestamp(comType)) {
            List<Timestamp> arr = new ArrayList<>();
            list.stream().forEach(o -> arr.add((Timestamp) o));
            FieldUtil.set(obj, field, value);
        }
    }

    private void decodePrimitive(Object obj, Class<?> type) {
        if (DataType.isFloat(type)) {
            FieldUtil.set(obj, field, Float.parseFloat(value.toString()));
        } else if (DataType.isFloatObject(type)) {
            FieldUtil.set(obj, field, Float.valueOf(value.toString()));
        } else if (DataType.isShort(type)) {
            FieldUtil.set(obj, field, Short.parseShort(value.toString()));
        } else if (DataType.isShortObject(type)) {
            FieldUtil.set(obj, field, Short.valueOf(value.toString()));
        } else if (DataType.isByte(type)) {
            FieldUtil.set(obj, field, Byte.parseByte(value.toString()));
        } else if (DataType.isByteObject(type)) {
            FieldUtil.set(obj, field, Byte.valueOf(value.toString()));
        } else if (DataType.isListType(type)) {
            FieldUtil.set(obj, field, moveCollectionElement(new ArrayList<>()));
        } else if (DataType.isSetType(type)) {
            FieldUtil.set(obj, field, moveCollectionElement(new HashSet<>()));
        } else if (DataType.isQueueType(type)) {
            FieldUtil.set(obj, field, moveCollectionElement(new LinkedList<>()));
        } else if (DataType.isChar(type)) {
            FieldUtil.set(obj, field, value.toString().charAt(0));
        } else if (DataType.isTimestamp(type)) {
            FieldUtil.set(obj, field, new Timestamp(((Date) value).getTime()));
        } else if (DataType.isLong(type)) {
            FieldUtil.set(obj, field, Long.parseLong(value.toString()));
        } else if (DataType.isLongObject(type)) {
            FieldUtil.set(obj, field, Long.valueOf(value.toString()));
        } else {
            FieldUtil.set(obj, field, value);
        }
    }

    private Collection moveCollectionElement(Collection collection) {
        List src = (ArrayList) value;
        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
        Class type = (Class) parameterizedType.getActualTypeArguments()[0];
        if (DataType.isShortObject(type)) {
            src.stream().forEach(o -> collection.add(Short.valueOf(o.toString())));
        } else if (DataType.isByteObject(type)) {
            src.stream().forEach(o -> collection.add(Byte.valueOf(o.toString())));
        } else if (DataType.isFloat(type)) {
            src.stream().forEach(o -> collection.add(Float.valueOf(o.toString())));
        } else if (DataType.isCharObject(type)) {
            src.stream().forEach(o -> collection.add(o.toString().charAt(0)));
        } else {
            src.stream().forEach(collection::add);
        }
        return collection;
    }
}
